package com.wondertek.common.exception;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import com.wondertek.common.base.Result;
import com.wondertek.common.util.base.MessageUtils;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import jakarta.validation.Path;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.resource.NoResourceFoundException;

import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author xqs
 */
@SuppressWarnings("all")
@Slf4j
@Configuration
public class GlobalExceptionHandler {

    @Order(0)
    @RestControllerAdvice
    public static class Web {

        @ResponseStatus(HttpStatus.NOT_FOUND)
        @ExceptionHandler(value = NoHandlerFoundException.class)
        public Result<?> noHandlerFoundException(NoHandlerFoundException e) {
            log.error("【全局异常拦截】NoHandlerFoundException: 请求路径 {}, 请求方法 {}", e.getRequestURL(), e.getHttpMethod());
            return Result.error(HttpStatus.NOT_FOUND.value(), "request.not.found");
        }

        @ResponseStatus(HttpStatus.NOT_FOUND)
        @ExceptionHandler(value = NoResourceFoundException.class)
        public Result<?> noResourceFoundException(NoResourceFoundException e) {
            log.error("【全局异常拦截】NoResourceFoundException: 请求路径 {}, 请求方法 {}", e.getResourcePath(), e.getHttpMethod());
            return Result.error(HttpStatus.NOT_FOUND.value(), "request.not.found");
        }

        @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
        @ExceptionHandler(value = HttpRequestMethodNotSupportedException.class)
        public Result<?> httpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
            HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
            log.error("【全局异常拦截】HttpRequestMethodNotSupportedException: 请求路径 {} 请求方法 {} 支持方法 {}", request.getRequestURL(), e.getMethod(), Objects.requireNonNull(e.getSupportedHttpMethods()).toString());
            return Result.error(HttpStatus.METHOD_NOT_ALLOWED.value(), "request.method.not.support");
        }

        @ResponseStatus(HttpStatus.BAD_REQUEST)
        @ExceptionHandler(value = MethodArgumentNotValidException.class)
        public Result<?> methodArgumentNotValidException(MethodArgumentNotValidException e) {
            log.error("【全局异常拦截】MethodArgumentNotValidException: 错误信息 {} ", e.getMessage(), e);
            BindingResult result = e.getBindingResult();
            JSONArray array = new JSONArray();
            if (result.hasErrors()) {
                List<ObjectError> errors = result.getAllErrors();
                errors.forEach(p -> {
                    FieldError fieldError = (FieldError) p;
                    JSONObject object = new JSONObject();
                    object.set("object", fieldError.getObjectName());
                    object.set("field", fieldError.getField());
                    object.set("error", MessageUtils.getMessage(fieldError.getDefaultMessage()));
                    array.add(object);
                });
            }
            Result<Object> error = (Result<Object>) Result.error(HttpStatus.BAD_REQUEST.value(), "parameter.exception");
            error.setData(array);
            List<String> errors = array.stream().map(var -> {
                JSONObject m = (JSONObject) var;
                return m.getStr("error");
            }).collect(Collectors.toList());
            error.setMessage(String.join(",", errors));
            return error;
        }

        @ResponseStatus(HttpStatus.BAD_REQUEST)
        @ExceptionHandler(BindException.class)
        public Result<?> BindException(BindException e) {
            log.error("【全局异常拦截】BindException: 错误信息 {} ", e.getMessage(), e);
            BindingResult result = e.getBindingResult();
            JSONArray array = new JSONArray();
            if (result.hasErrors()) {
                List<ObjectError> errors = result.getAllErrors();
                errors.forEach(p -> {
                    FieldError fieldError = (FieldError) p;
                    JSONObject object = new JSONObject();
                    object.set("object", fieldError.getObjectName());
                    object.set("field", fieldError.getField());
                    object.set("error", MessageUtils.getMessage(fieldError.getDefaultMessage()));
                    array.add(object);
                });
            }
            Result<Object> error = (Result<Object>) Result.error(HttpStatus.BAD_REQUEST.value(), "parameter.exception");
            error.setData(array);
            List<String> errors = array.stream().map(var -> {
                JSONObject m = (JSONObject) var;
                return m.getStr("error");
            }).collect(Collectors.toList());
            error.setMessage(String.join(",", errors));
            return error;
        }

        @ResponseStatus(HttpStatus.BAD_REQUEST)
        @ExceptionHandler(ConstraintViolationException.class)
        public Result<?> handleConstraintViolationException(ConstraintViolationException ex) {
            log.error("【全局异常拦截】ConstraintViolationException: 错误信息 {} ", ex.getMessage(), ex);
            // 提取所有违反约束的消息
            Set<ConstraintViolation<?>> violations = ex.getConstraintViolations();
            JSONArray array = new JSONArray();
            violations.forEach(violation -> {
                JSONObject object = new JSONObject();
                Path path = violation.getPropertyPath();
                object.set("field", StrUtil.subAfter(violation.getPropertyPath().toString(), ".", false));
                object.set("error", MessageUtils.getMessage(violation.getMessage()));
                array.add(object);
            });
            Result<Object> error = (Result<Object>) Result.error(HttpStatus.BAD_REQUEST.value(), "parameter.exception");
            error.setData(array);
            List<String> errors = array.stream().map(var -> {
                JSONObject m = (JSONObject) var;
                return m.getStr("error");
            }).collect(Collectors.toList());
            error.setMessage(String.join(",", errors));
            return error;
        }

        @ResponseStatus(HttpStatus.BAD_REQUEST)
        @ExceptionHandler(value = MissingServletRequestParameterException.class)
        public Result<?> missingServletRequestParameterException(MissingServletRequestParameterException e) {
            log.error("【全局异常拦截】MethodArgumentTypeMismatchException: 参数名 {}, 错误信息 {}", e.getParameterName(), e.getMessage(), e);
            return Result.error(HttpStatus.BAD_REQUEST.value(), "parameter.exception");
        }

        @ResponseStatus(HttpStatus.BAD_REQUEST)
        @ExceptionHandler(value = HttpMessageNotReadableException.class)
        public Result<?> httpMessageNotReadableException(HttpMessageNotReadableException e) {
            log.error("【全局异常拦截】HttpMessageNotReadableException: 错误信息 {}", e.getMessage(), e);
            return Result.error(HttpStatus.BAD_REQUEST.value(), "parameter.exception");
        }

        @ResponseStatus(HttpStatus.BAD_REQUEST)
        @ExceptionHandler(value = MaxUploadSizeExceededException.class)
        public Result<?> maxUploadSizeExceededException(MaxUploadSizeExceededException e) {
            log.error("【全局异常拦截】MaxUploadSizeExceededException: 错误信息 {}", e.getMessage(), e);
            return Result.error(HttpStatus.BAD_REQUEST.value(), "upload.exceed.maxSize");
        }

    }


    @Order(0)
    @RestControllerAdvice
    public static class Security {

        @ResponseStatus(HttpStatus.UNAUTHORIZED)
        @ExceptionHandler(value = AuthenticationException.class)
        public Result authenticationException(AuthenticationException e) {
            log.error("【全局异常拦截】AuthenticationException: 错误信息 {}", e.getMessage());
            return Result.error(HttpStatus.UNAUTHORIZED.value(), e.getMessage());
        }

        @ResponseStatus(HttpStatus.FORBIDDEN)
        @ExceptionHandler(value = AccessDeniedException.class)
        public Result accessDeniedException(AccessDeniedException e) {
            log.error("【全局异常拦截】AccessDeniedException: 错误信息 {}", e.getMessage());
            return Result.error(HttpStatus.FORBIDDEN.value(), e.getMessage());
        }
    }


    @Order(0)
    @RestControllerAdvice
    @ConditionalOnClass(DuplicateKeyException.class)
    public static class Dao {

        @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
        @ExceptionHandler(value = DuplicateKeyException.class)
        public Result duplicateKeyException(DuplicateKeyException e) {
            log.error("【全局异常拦截】DuplicateKeyException: 错误信息 {}", e.getMessage());
            return Result.error("database.insert.repeat");
        }

        @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
        @ExceptionHandler(value = DataIntegrityViolationException.class)
        public Result dataIntegrityViolationException(DataIntegrityViolationException e) {
            if (e.getCause() instanceof ConstraintViolationException) {
                log.error("【全局异常拦截】ConstraintViolationException: 错误信息 {}", e.getMessage());
                return Result.error("primary.key.error");
            }
            log.error("【全局异常拦截】DataIntegrityViolationException: 错误信息 {}", e.getMessage());
            return Result.error("field.too.long");
        }
    }

    @Order(1)
    @RestControllerAdvice
    public static class Base {

        @ExceptionHandler(value = AppException.class)
        public Result<?> baseException(AppException e) {
            log.error("【全局异常拦截】AppException : 状态码 {}, 错误信息 {}", e.getCode(), MessageUtils.getMessage(e.getMessage()), e);
            return Result.error(e.getCode(), e.getMessage());
        }

        @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
        @ExceptionHandler(value = Exception.class)
        public Result<?> exceptionHandler(Exception e) {
            log.error(String.format("【全局异常拦截】未定义类型 %s:  错误信息 %s", e.getClass().getSimpleName(), e.getMessage()), e);
            return Result.error("unknown.error");
        }

    }


}
